﻿#pragma once

#include <cstdint>
#include <stdexcept>
#include <string>
#include <vector>

namespace asycipc {

constexpr uint16_t kFrameMagic = 0x4150;  // "AP"
constexpr uint8_t kProtocolVersion = 0x01;
constexpr uint8_t kFrameFlagAckRequired = 0x04;
constexpr uint8_t kFrameFlagFragment = 0x08;

constexpr std::size_t kMaxFramePayloadBytes = 32 * 1024;

enum class FrameType : uint8_t {
  kData = 0x00,
  kAck = 0x01,
  kNack = 0x02,
  kHello = 0x03,
  kHelloAck = 0x04,
  kHeartbeat = 0x05,
  kHeartbeatAck = 0x06,
  kBye = 0x07,
};

struct Frame {
  uint16_t magic = kFrameMagic;
  uint8_t version = kProtocolVersion;
  FrameType type = FrameType::kData;
  uint8_t flags = 0;
  uint8_t reserved = 0;
  uint64_t seq_no = 0;
  uint32_t payload_length = 0;
  uint32_t header_checksum = 0;
  uint16_t ext_length = 0;
  std::vector<uint8_t> extensions;
  std::vector<uint8_t> payload;
  uint32_t payload_checksum = 0;

  std::vector<uint8_t> Serialize() const;
  static Frame Deserialize(const std::vector<uint8_t>& raw);

  static Frame MakeData(uint64_t seq, const std::vector<uint8_t>& payload,
                        const std::vector<uint8_t>& extensions = {},
                        uint8_t flags = 0);
  static Frame MakeAck(uint64_t seq);
  static Frame MakeNack(uint64_t seq, const std::string& reason = {});
  static Frame MakeHeartbeat();
  static Frame MakeHeartbeatAck();
};

class FrameValidationError : public std::runtime_error {
 public:
  using std::runtime_error::runtime_error;
};

}  // namespace asycipc
